/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Description: implement of hal_token
 */

#include "hal_token.h"
#include "ohos_errno.h"
#include "ohos_types.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "securec.h"
#include "iot_flash.h"
#include "device_profile.h"

#define BITS_PER_BYTE 8
// sector size is 4096 Bytes
#define SECTOR_ALIGN_BYTES 4096
// token's max length is 151 Bytes, we using 256 Bytes to erase each token aera
#define MAX_TOKEN_AREA_SIZE 256
#define TOKEN_SIZE 151
// 4 Bytes for token flag
// if token's both area are available, when read token, always return area which flag is bigger;
// and recover area which flag is small while write token.
#define TOKEN_FLAG_SIZE 4
#define TOKEN_WITH_FLAG_SIZE (TOKEN_SIZE + TOKEN_FLAG_SIZE)
// 4 Bytes for token magic number, if data not in {1,2,3,4} order means the token area is not initialled.
// if token area is initialled, the token magic number's next Byte data is token actual value.
static const char g_tokenMagicNum[] = {1, 2, 3, 4};
#define TOKEN_MAGIC_NUM_SIZE (sizeof(g_tokenMagicNum) / sizeof(g_tokenMagicNum[0]))
// token flag offset address on HAL_PARTITION_HARMONYOS_TOKEN
#define TOKEN_ADDR 0x010D0000
// token area A offset address on HAL_PARTITION_HARMONYOS_TOKEN
#define TOKEN_A_ADDR TOKEN_ADDR
// token area B offset address on HAL_PARTITION_HARMONYOS_TOKEN
#define TOKEN_B_ADDR (TOKEN_A_ADDR + SECTOR_ALIGN_BYTES)

/* *
 * @brief Write token raw data to the flash, and this function is only for token read and write.
 * @param start The start address of write area on flash
 * @param tokenRawData The token raw data need to be wrote.
 * @param len The lenght of the data need to be wrote.
 * @returns 0 if it succeed and write data to the flash, others value is falied.
 */
static int32_t FlashWriteTokenRawData(uint32_t start, const char *tokenRawData, uint32_t len)
{
    if (start % SECTOR_ALIGN_BYTES != 0) {
        printf("Token : Unsupport address not align yet, may cause data overlap error\n");
        return -1;
    }
    if (tokenRawData == NULL || len <= 0) {
        printf("Token : Invalid input of flash write\n");
        return -1;
    }

    if (IoTFlashErase(start,len) != 0) {
        printf("Token : flash erase error\n");
        return -1;
    }

    if (IoTFlashWrite(start, len, (const uint8_t *)tokenRawData, 0) == 0) {
        printf("Token : Write data to falsh failed\n");
        return -1;
    }

    return 0;
}

/* *
 * @brief Read data from the flash, and this function is only for token read and write.
 * @param start The start address of erase area on flash
 * @param tokenRawData The raw token data need to be read.
 * @param len The length of the data need to be wrote.
 * @returns    0 if it succeeds and erase the area with data 0;
 * -1 if it fails, the actual data on the ease area is unknown.
 */
static int32_t FlashReadTokenRawData(uint32_t start, char *tokenRawData, uint32_t len)
{
    if (start % SECTOR_ALIGN_BYTES != 0) {
        printf("Token : Unsupport start address not align yet, may cause data overlap error.\n");
        return -1;
    }
    if (tokenRawData == NULL || len <= 0) {
        printf("Token : Invalid input of flash read.\n");
        return -1;
    }

    if (IoTFlashRead(start, len, (const uint8_t *)tokenRawData) == 0) {
        printf("Token : Flash read failed\n");
        return -1;
    }

    return 0;
}

static int32_t ReadTokenWithFlag(uint32_t start, char *result, uint32_t len)
{
    const uint32_t buffLen = TOKEN_MAGIC_NUM_SIZE + TOKEN_WITH_FLAG_SIZE + 1;
    char buf[buffLen];
    (void)memset_s(buf, buffLen, 0, buffLen);
    if (FlashReadTokenRawData(start, buf, buffLen) != 0) {
        printf("Token : Read flash token area failed\n");
        return -1;
    }
    int32_t isTokenValid = 1;
    // check is initialed or not
    for (uint32_t i = 0; i < TOKEN_MAGIC_NUM_SIZE; i++) {
        if (buf[i] != g_tokenMagicNum[i]) {
            isTokenValid = 0;
            break;
        }
    }
    if (isTokenValid == 0) {
        // token area isn't valid
        printf("The token area is invalid\n");
        return -1;
    }
    (void)memcpy_s(result, TOKEN_WITH_FLAG_SIZE, buf + TOKEN_MAGIC_NUM_SIZE, TOKEN_WITH_FLAG_SIZE);
    return 0;
}

static int32_t WriteTokenWithFlag(uint32_t start, const char *tokenWithFlag, uint32_t len)
{
    const uint32_t buffLen = TOKEN_MAGIC_NUM_SIZE + TOKEN_WITH_FLAG_SIZE + 1;
    char buf[buffLen];
    (void)memset_s(buf, buffLen, 0, buffLen);

    for (uint32_t i = 0; i < TOKEN_MAGIC_NUM_SIZE; i++) {
        buf[i] = g_tokenMagicNum[i];
    }
    (void)memcpy_s(buf + TOKEN_MAGIC_NUM_SIZE, TOKEN_WITH_FLAG_SIZE, tokenWithFlag, TOKEN_WITH_FLAG_SIZE);
    if (FlashWriteTokenRawData(start, buf, buffLen) != 0) {
        printf("Token : Write flash token area failed.\n");
        return -1;
    }
    return 0;
}

static uint32_t GetTokenFlag(const char tokenWithFlag[])
{
    uint32_t result = 0;
    for (uint32_t i = 0; i < TOKEN_FLAG_SIZE; i++) {
        result |= ((uint8_t)tokenWithFlag[TOKEN_SIZE + i]) << ((TOKEN_FLAG_SIZE - 1 - i) * BITS_PER_BYTE);
    }
    return result;
}

static void SetTokenFlag(uint8_t flag[], uint32_t value)
{
    for (uint32_t i = 0; i < TOKEN_FLAG_SIZE; i++) {
        flag[i] = (value >> (BITS_PER_BYTE * (TOKEN_FLAG_SIZE - 1 - i))) & 0xFF;
    }
}

/* *
 * @brief Read token value from the flash token A or B area, and this function is only for token read and write.
 *
 * @param token The data buffer malloced by caller.
 * @param len The data buffer length.
 *
 * @returns    -1 if it fails, the actual data is unknown.
 * 0 if it succeeds and means read token from area A or area B's data.
 * -2 if it succeeds and means current is no token exist on the device.
 */
static int32_t OEMReadToken(char *token, uint32_t len)
{
    if (token == NULL || len < 0) {
        return -1;
    }
    char tokenWithFlagA[TOKEN_WITH_FLAG_SIZE] = {0};
    char tokenWithFlagB[TOKEN_WITH_FLAG_SIZE] = {0};
    int32_t retA = ReadTokenWithFlag(TOKEN_A_ADDR, tokenWithFlagA, TOKEN_WITH_FLAG_SIZE);
    int32_t retB = ReadTokenWithFlag(TOKEN_B_ADDR, tokenWithFlagB, TOKEN_WITH_FLAG_SIZE);
    if ((retA != 0) && (retB != 0)) {
        printf("Read Token : Using device info to generate online token.\n");
        // -2 means current is no token exist on the device
        return -2;
    } else if ((retA == 0) && (retB != 0)) {
        // token area A has data, area B is NULL, return A;
        (void)memcpy_s(token, len, tokenWithFlagA, len);
        return 0;
    } else if ((retA != 0) && (retB == 0)) {
        // token area B has data, area A is NULL, return B;
        (void)memcpy_s(token, len, tokenWithFlagB, len);
        return 0;
    } else {
        // token area A and B both have data, return area which flag is larger than the other one.
        uint32_t flagA = GetTokenFlag(tokenWithFlagA);
        uint32_t flagB = GetTokenFlag(tokenWithFlagB);
        if (flagA > flagB) {
            (void)memcpy_s(token, len, tokenWithFlagA, len);
            return 0;
        } else {
            (void)memcpy_s(token, len, tokenWithFlagB, len);
            return 0;
        }
    }
}

/* *
 * @brief Write token value to the token A or B area on the flash, and this function is only for token read and write.
 *
 * @param token The input token data.
 * @param len The token's length.
 *
 * @returns    -1 if it fails, write token failed.
 * 0 if it succeeds and means write token to area A or area B's data.
 */
static int32_t OEMWriteToken(const char *token, uint32_t len)
{
    if ((token == NULL) || (len < 0)) {
        return -1;
    }
    char tokenWithFlagA[TOKEN_WITH_FLAG_SIZE] = {0};
    char tokenWithFlagB[TOKEN_WITH_FLAG_SIZE] = {0};
    int32_t retA = ReadTokenWithFlag(TOKEN_A_ADDR, tokenWithFlagA, TOKEN_WITH_FLAG_SIZE);
    int32_t retB = ReadTokenWithFlag(TOKEN_B_ADDR, tokenWithFlagB, TOKEN_WITH_FLAG_SIZE);
    if ((retA != 0) && (retB != 0)) {
        // no token on the device, write token to A area;
        printf("Write Token : no token data on device\n");
        uint8_t flag[TOKEN_FLAG_SIZE] = {0};
        if ((memcpy_s(tokenWithFlagA, TOKEN_WITH_FLAG_SIZE, token, len) != 0) ||
            (memcpy_s(tokenWithFlagA + len, TOKEN_WITH_FLAG_SIZE - len, flag, TOKEN_FLAG_SIZE) != 0)) {
            return -1;
        }
        if (WriteTokenWithFlag(TOKEN_A_ADDR, tokenWithFlagA, TOKEN_WITH_FLAG_SIZE) != 0) {
            printf("Write Token : flash write token area A failed.\n");
            return -1;
        }
        return 0;
    } else if ((retA == 0) && (retB != 0)) {
        // token area A has data, area B is NULL, write token to B area;
        (void)memset_s(tokenWithFlagB, TOKEN_WITH_FLAG_SIZE, 0, TOKEN_WITH_FLAG_SIZE);
        uint32_t flagA = GetTokenFlag(tokenWithFlagA);
        uint8_t flag[TOKEN_FLAG_SIZE] = {0};
        SetTokenFlag(flag, (uint32_t)(flagA + 1));
        if ((memcpy_s(tokenWithFlagB, TOKEN_WITH_FLAG_SIZE, token, len) != 0) ||
            (memcpy_s(tokenWithFlagB + len, TOKEN_WITH_FLAG_SIZE, flag, TOKEN_FLAG_SIZE) != 0)) {
            return -1;
        }
        if (WriteTokenWithFlag(TOKEN_B_ADDR, tokenWithFlagB, TOKEN_WITH_FLAG_SIZE) != 0) {
            printf("Write Token : flash write token area B failed.\n");
            return -1;
        }
        return 0;
    } else if ((retA != 0) && (retB == 0)) {
        // token area B has data, area A is NULL, write token to A area;
        (void)memset_s(tokenWithFlagA, TOKEN_WITH_FLAG_SIZE, 0, TOKEN_WITH_FLAG_SIZE);
        uint32_t flagB = GetTokenFlag(tokenWithFlagB);
        uint8_t flag[TOKEN_FLAG_SIZE] = {0};
        SetTokenFlag(flag, (uint32_t)(flagB + 1));
        if ((memcpy_s(tokenWithFlagA, TOKEN_WITH_FLAG_SIZE, token, len) != 0) ||
            (memcpy_s(tokenWithFlagA + len, TOKEN_WITH_FLAG_SIZE, flag, TOKEN_FLAG_SIZE) != 0)) {
            return -1;
        }
        if (WriteTokenWithFlag(TOKEN_A_ADDR, tokenWithFlagA, TOKEN_WITH_FLAG_SIZE) != 0) {
            printf("Write Token : flash write token area A failed.\n");
            return -1;
        }
        return 0;
    } else {
        // token area A and B both have data, write token to the area which flag is smaller than the other one.
        uint32_t flagA = GetTokenFlag(tokenWithFlagA);
        uint32_t flagB = GetTokenFlag(tokenWithFlagB);
        if (flagA > flagB) {
            // area A's token is new, recover area B;
            (void)memset_s(tokenWithFlagB, TOKEN_WITH_FLAG_SIZE, 0, TOKEN_WITH_FLAG_SIZE);
            uint8_t flag[TOKEN_FLAG_SIZE] = {0};
            SetTokenFlag(flag, (uint32_t)(flagA + 1));
            if ((memcpy_s(tokenWithFlagB, TOKEN_WITH_FLAG_SIZE, token, len) != 0) ||
                (memcpy_s(tokenWithFlagB + len, TOKEN_WITH_FLAG_SIZE, flag, TOKEN_FLAG_SIZE) != 0)) {
                return -1;
            }
            if (WriteTokenWithFlag(TOKEN_B_ADDR, tokenWithFlagB, TOKEN_WITH_FLAG_SIZE) != 0) {
                printf("Write Token : flash write token area B failed.\n");
                return -1;
            }
            return 0;
        } else {
            // area B's token is new, recover area A;
            (void)memset_s(tokenWithFlagA, TOKEN_WITH_FLAG_SIZE, 0, TOKEN_WITH_FLAG_SIZE);
            uint8_t flag[TOKEN_FLAG_SIZE] = {0};
            SetTokenFlag(flag, (uint32_t)(flagB + 1));
            if ((memcpy_s(tokenWithFlagA, TOKEN_WITH_FLAG_SIZE, token, len) != 0) ||
                (memcpy_s(tokenWithFlagA + len, TOKEN_WITH_FLAG_SIZE, flag, TOKEN_FLAG_SIZE) != 0)) {
                return -1;
            }
            if (WriteTokenWithFlag(TOKEN_A_ADDR, tokenWithFlagA, TOKEN_WITH_FLAG_SIZE) != 0) {
                printf("Write Token : flash write token area A failed.\n");
                return -1;
            }
            return 0;
        }
    }
}

static int32_t OEMGetAcKey(char *acKey, uint32_t len)
{
    if ((acKey == NULL) || (len < 0)) {
        printf("Invalid input parameters\n");
        return EC_FAILURE;
    }
    if (memcpy_s(acKey, len, PRODUCT_ACKEY, sizeof(PRODUCT_ACKEY)) != 0) {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

static int32_t OEMGetProdId(char *productId, uint32_t len)
{
    if ((productId == NULL) || (len < 0)) {
        printf("Invalid input parameters\n");
        return EC_FAILURE;
    }
    if (memcpy_s(productId, len, PRODUCT_ID, strlen(PRODUCT_ID)) != 0) {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

static int32_t OEMGetProdKey(char *productKey, uint32_t len)
{
    if ((productKey == NULL) || (len < 0)) {
        printf("Invalid input parameters\n");
        return EC_FAILURE;
    }
    if (memcpy_s(productKey, len, PRODUCT_PRODKEY, strlen(PRODUCT_PRODKEY)) != 0) {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

int32_t HalReadToken(char *token, uint32_t len)
{
    if (token == NULL) {
        return EC_FAILURE;
    }
    return OEMReadToken(token, len);
}

int32_t HalWriteToken(const char *token, uint32_t len)
{
    if (token == NULL) {
        return EC_FAILURE;
    }
    return OEMWriteToken(token, len);
}
int32_t HalGetAcKey(char *acKey, uint32_t len)
{
    if (acKey == NULL) {
        return EC_FAILURE;
    }
    return OEMGetAcKey(acKey, len);
}
int32_t HalGetProdId(char *productId, uint32_t len)
{
    if (productId == NULL) {
        return EC_FAILURE;
    }
    return OEMGetProdId(productId, len);
}

int32_t HalGetProdKey(char *productKey, uint32_t len)
{
    if (productKey == NULL) {
        return EC_FAILURE;
    }
    return OEMGetProdKey(productKey, len);
}